package chain

import (
	"bytes"
	"crypto/ecdsa"
	"encoding/json"
	"fmt"
	"github.com/simplechain-org/go-simplechain/common"
	"github.com/simplechain-org/go-simplechain/common/compiler"
	"github.com/simplechain-org/go-simplechain/common/hexutil"
	"github.com/simplechain-org/go-simplechain/core/types"
	"math/big"
	"os/exec"
	"regexp"
	"strconv"
	"strings"
)

// DeployContract 部署合约
func (c *Client) DeployContract(contractData string, from common.Address, gasLimit uint64, gasPrice *big.Int, privateKey *ecdsa.PrivateKey, chainId *big.Int) (txHash, contractAddr string, err error) {
	nonce, err := c.GetNonce(from)
	if err != nil {
		return "", "", err
	}
	txid, err := c.SendTransaction(CreateContract, nonce, "", "0", gasLimit, gasPrice, common.FromHex(contractData), privateKey, chainId)
	if err != nil {
		return "", "", err
	}
	opType := "DeployContract"
	dones := make(chan error, 1)
	receipt := make(chan types.Receipt, 1)
	go c.JudgeupchainstatusFast(*txid, opType, receipt, dones)
	for i := 0; i < 1; i++ {
		done := <-dones
		if done != nil {
			return "", "", done
		}
	}
	var receiptRes types.Receipt
	for i := 0; i < 1; i++ {
		receiptRes = <-receipt
	}
	contractAddress := hexutil.Encode(receiptRes.ContractAddress[:])
	return *txid, contractAddress, nil
}

var versionRegexp = regexp.MustCompile(`([0-9]+)\.([0-9]+)\.([0-9]+)`)

// SolidityVersion runs solc and parses its version output.
func SolidityVersion(solc string) (*compiler.Solidity, error) {
	if solc == "" {
		solc = "solc"
	}
	var out bytes.Buffer
	cmd := exec.Command(solc, "--version")
	cmd.Stdout = &out
	err := cmd.Run()
	if err != nil {
		return nil, err
	}
	matches := versionRegexp.FindStringSubmatch(out.String())
	if len(matches) != 4 {
		return nil, fmt.Errorf("can't parse solc version %q", out.String())
	}
	s := &compiler.Solidity{Path: cmd.Path, FullVersion: out.String(), Version: matches[0]}
	if s.Major, err = strconv.Atoi(matches[1]); err != nil {
		return nil, err
	}
	if s.Minor, err = strconv.Atoi(matches[2]); err != nil {
		return nil, err
	}
	if s.Patch, err = strconv.Atoi(matches[3]); err != nil {
		return nil, err
	}
	return s, nil
}
func MakeArgs(s *compiler.Solidity) []string {
	p := []string{
		"--combined-json", "bin,bin-runtime,srcmap,srcmap-runtime,abi,userdoc,devdoc",
		"--optimize",                  // code optimizer switched on
		"--allow-paths", "., ./, ../", // default to support relative paths
	}
	if s.Major > 0 || s.Minor > 4 || s.Patch > 6 {
		p[1] += ",metadata,hashes"
	}
	return p
}

func (c *Client) CompilerContract(solc, source string) (map[string]string, error) {
	solidity, err := SolidityVersion(solc)
	if err != nil {
		return nil, err
	}
	args := append(MakeArgs(solidity), "--")
	cmd := exec.Command(solidity.Path, append(args, "-")...)
	cmd.Stdin = strings.NewReader(source)
	var stderr, stdout bytes.Buffer
	cmd.Stderr = &stderr
	cmd.Stdout = &stdout
	if err := cmd.Run(); err != nil {
		return nil, fmt.Errorf("solc: %v\n%s", err, stderr.Bytes())
	}
	contracts, err := compiler.ParseCombinedJSON(stdout.Bytes(), source, solidity.Version, solidity.Version, strings.Join(MakeArgs(solidity), " "))
	if err != nil {
		return nil, fmt.Errorf("failed to read contract information from json output: %v\n", err)
	}
	if len(contracts) != 1 {
		return nil, fmt.Errorf("one contract expected, got %d", len(contracts))
	}
	contractMap := make(map[string]string)
	for name, contract := range contracts {
		_, err := json.Marshal(contract.Info.AbiDefinition) // Flatten the compiler parse
		if err != nil {
			return nil, fmt.Errorf("failed to parse ABIs from compiler output: %v", err)
		}
		contractMap[name] = contract.Code
	}
	return contractMap, nil
}
